package com.headcaselabs.lb.director;

import java.io.Serializable;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import com.headcaselabs.lb.client.PowerPipe;
import com.headcaselabs.lb.common.Record;
import com.headcaselabs.lb.common.Server;
/* 
Copyright 2007, HeadCase Humanufacturing, Inc.

	Licensed under the Apache License, Version 2.0 (the "License"); you
	may not use this file except in compliance with the License. You may
	obtain a copy of the License at
	
	   http://www.apache.org/licenses/LICENSE-2.0
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
	implied. See the License for the specific language governing
	permissions and limitations under the License.
*/
/**
 * RoundRobin where the starting index is selected at random.
 * @author Florian Leibert
 */

public class RandomizedRoundRobin implements Serializable, LoadBalancedRecord {
	
	private static final long	serialVersionUID	= -4288483887981975877L;
	private List<Record> records = null;
	private int pointer = 0;
	private String name;
	private long timestamp;
	private long lastUpdate;
	//The Group ID is only used on the director side and is unimportant for equals() and hashCode().
	//it may be overwritten by the director
	private int id;
	private Random myRandom;
	

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#getLastUpdate()
	 */
	public long getLastUpdate() {
		return lastUpdate;
	}

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#setLastUpdate(long)
	 */
	public void setLastUpdate(long lastUpdate) {
		this.lastUpdate = lastUpdate;
	}

	public RandomizedRoundRobin() {
		this.timestamp = System.currentTimeMillis();
		this.lastUpdate = 0;
		myRandom = new Random();
	}
	
	public RandomizedRoundRobin(String name, Random random) {
		this.name = name;
		this.timestamp = System.currentTimeMillis();
		myRandom = random;
	}
	
	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#buildEntries(java.util.List)
	 */
	public void buildEntries(List<Server> members) {
		lastUpdate = (new Date()).getTime();
		records = new LinkedList<Record>();
		for (Server s : members) {
			Record r = new Record(name,s.getRecordType());
			if (s.getRecordType().equals("A"))
				r.getContent().add(s.getIp());
			else if (s.getRecordType().equals("CNAME"))
				r.getContent().add(s.getFqdn());
			else 
				throw new RuntimeException("Invalid server mode");
			r.setTtl(Record.DEFAULT_DYN_TTL);
			records.add(r);
		}
		//Distribute the initial selection
		if (members.size()>0)
			pointer = myRandom.nextInt(records.size());
	}
	
	
	
	
	//TODO: implement something besides RoundRobin
	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#next()
	 */
	public Record next() {
		if (records==null||records.size()==0) {
			log("LOG\t Error. Encountered a loadbalancing record but no nodes are registered.",PowerPipe.LOG_INFO);
			return null;
		}
		pointer++;
		if (pointer>=records.size()) {
			pointer = 0;
		}
		return records.get(pointer);
	}
	

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#getName()
	 */
	public String getName() {
		return name;
	}

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#setName(java.lang.String)
	 */
	public void setName(String name) {
		this.name = name;
	}

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#getTimestamp()
	 */
	public long getTimestamp() {
		return timestamp;
	}

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#setTimestamp(long)
	 */
	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}
	
	

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#getId()
	 */
	public int getId() {
		return id;
	}

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#setId(int)
	 */
	public void setId(int id) {
		this.id = id;
	}
	
	public void log(String msg, int logLevel) {
		System.err.print(msg);
	}

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#getRecords()
	 */
	public List<Record> getRecords() {
		return records;
	}

	/* (non-Javadoc)
	 * @see com.headcaselabs.lb.director.LoadBalancedRecord#setRecords(java.util.List)
	 */
	public void setRecords(List<Record> records) {
		this.records = records;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = super.hashCode();
		result = PRIME * result + (int) (lastUpdate ^ (lastUpdate >>> 32));
		result = PRIME * result + ((name == null) ? 0 : name.hashCode());
		result = PRIME * result + (int) (timestamp ^ (timestamp >>> 32));
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		final RandomizedRoundRobin other = (RandomizedRoundRobin) obj;
		if (lastUpdate != other.lastUpdate)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (timestamp != other.timestamp)
			return false;
		return true;
	}

	

}
